﻿// MIT License (MIT)
// 
// Copyright (c) 2014 K Ercan Turkarslan
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// 

using System;
using System.IO;
using System.Linq;
using CodeGen;
using CollectionsLibrary;
using Mios;
using Mios.MiosConnection;

namespace MiosGenerator
{
    public static class MiosFiles
    {
        #region Static Property OutputFolder : Folder;
        private static Folder _outputFolder;
        internal static Folder OutputFolder
        {
            get
            {
                if (_outputFolder == null)
                    throw new InvalidOperationException("MiosFiles.OutputFolder has to be initialized!");

                return _outputFolder;
            }
        }

        private static void SetOutputFolder(String outputFolder)
        {
            if (_outputFolder != null)
                throw new InvalidOperationException("OutputFolder was already initialized!");
                
            _outputFolder = new Folder(outputFolder);                           
        }
        #endregion        
        
        #region Static Property Engine : MiosEngine;

        private static MiosEngine _engine;

        internal static MiosEngine Engine
        {
            get { return _engine;  }   
        }

        private static void SetMiosEngine(Ui5Controller controller)
        {
            if (_engine != null)
                throw new InvalidOperationException("MiosEngine was already initialized!");

            _engine = new MiosEngine(controller);            
        }
        #endregion

        #region LoadFiles and LoadFile methods

        internal static MiosFile LoadFile(String filename)
        {
            var miosFile = GetFile(filename);

            if (miosFile != null)
                return miosFile;

            if (!OutputFolder.FileExists(filename))
            {
                // If file already exists in the output folder, we don't try to download it.
                // This will modified files.

                if (_engine == null)
                {
                    // We don't have a Mios Engine that we can connect to retrieve the file.
                    return null;
                }

                // We will try to download the file from Mios Engine.
                var fileContent = _engine.Luup_File(filename);

                if (String.IsNullOrEmpty(fileContent))
                {
                    // Unfortunately, we could not download the file from Mios Engine either!
                    return null;
                }

                // We are writing the file to output folder.
                using (var outF = new StreamWriter(OutputFolder.FullFilename(filename), false))
                {
                    outF.WriteLine(fileContent);
                }                               
            }

            switch (TypeOfMiosFile(filename))
            {
                case Types.DeviceFile:
                {
                    miosFile = new DeviceFile(filename);
                    break;
                }
                case Types.ServiceFile:
                {
                    miosFile = new ServiceFile(filename);
                    break;
                }
                default:
                    return null;
            }

            AddFile(miosFile);

            return miosFile;
        }

        #region DownloadFilesFromUserData method
        private static void DownloadFilesFromUserData()
        {
            if (_engine == null)
                return;

            var userData = XmlExtensions.TryParse(_engine.Luup_UserData("xml"));

            if (userData == null)
                return;

            foreach (var deviceNode in userData.Root.GetNode("devices").Nodes())
            {
                var deviceFilename = deviceNode.Attributes("device_file");

                if (String.IsNullOrEmpty(deviceFilename))
                    continue;

                var deviceFile = (DeviceFile) LoadFile(deviceFilename);

                deviceFile.ParseImplementationDetails(deviceNode);
            }
        }
        #endregion

        public static void LoadFiles(Ui5Controller controller, String folder, String listOfFiles)
        {
            SetOutputFolder(folder);
            SetMiosEngine(controller);

            if ((_serviceFiles != null) || (_deviceFiles != null))
                throw new InvalidOperationException("File list were already initialized!");

            _serviceFiles = new ItemsCollection<ServiceFile>();
            _deviceFiles = new ItemsCollection<DeviceFile>();

            if (!String.IsNullOrEmpty(listOfFiles))
            {
                if (!File.Exists(listOfFiles))
                {
                    // The file with provided name was not found. We can not proceed!
                    throw new FileNotFoundException(String.Format("List of files [{0}] could not be found!",
                        listOfFiles));
                }

                using (var sr = File.OpenText(listOfFiles))
                {
                    String inStr;

                    while ((inStr = sr.ReadLine()) != null)
                    {
                        var filename = inStr.Trim();

                        if (TypeOfMiosFile(filename) == Types.NotSupported)
                            continue;

                        LoadFile(filename);
                    }
                }
            }

            // If a device file was loaded in prior steps, but located in the output folder, we will load it.
            foreach (var fullFilename in Directory.EnumerateFiles(_outputFolder.Name, "D_*.Xml"))
            {
                LoadFile(Path.GetFileName(fullFilename));
            }

            // If a service file was loaded in prior steps, but located in the output folder, we will load it.
            foreach (var fullFilename in Directory.EnumerateFiles(_outputFolder.Name, "S_*.Xml"))
            {
                LoadFile(Path.GetFileName(fullFilename));
            }

            // This method will download user_data from Mios Engine. 
            // Create any Device File that was not created yet.
            // Update the Supported Properties for created device files.
            DownloadFilesFromUserData();
        }   
        #endregion

        #region Generate Method
        private static void GenerateMiosDeviceClass(String generateFolder)
        {
            var cb = CodeBlock.NewCodeFile("K Ercan Turkarslan", "2014");

            cb.Insert("#if USE_SDATA || USE_USERDATA");

            cb.Using("System");
            cb.Using("Mios.Generated.DeviceFiles");
            cb.NewLine();
            cb.Namespace_Begin("Mios.Generated");

                cb.Comment("MiosDeviceCreator : Create MiosDevice instance with provided DeviceType");

                cb.Class_Begin("public static", "MiosDeviceCreator");

                    cb.Method_Begin("public static", "Create", "MiosEngine engine, UInt32 id, String name, String localUdn, String deviceType, MiosRoom room", "MiosDevice");

                        cb.CheckNullParameter("engine");
                        cb.CheckNullOrEmptyStringParameter("deviceType");
                        cb.CheckNullParameter("room");

                        cb.If("String.IsNullOrEmpty(name)");
                        cb.InsertIndented("name = String.Format(\"Unnamed Device {0}\", id);");                        
                        cb.NewLine();

                        cb.Switch_Begin("deviceType");

                            foreach(var deviceFile in _deviceFiles.SortedItems)
                            {
                                cb.Switch_Case(deviceFile.DeviceType);
                                cb.Switch_Case_Return(
                                    String.Format("new {0}(engine, id, name, localUdn, deviceType, room)", deviceFile.ClassName));

                                cb.NewLine();
                            }

                            cb.Switch_Default();
                            cb.Switch_Case_Return("new MiosDevice(engine, id, name, localUdn, deviceType, room)");

                        cb.Switch_End();
                    cb.Method_End("Create");

                cb.Class_End();

            cb.Namespace_End();

            cb.Insert("#endif");

            cb.WriteToFile(Path.Combine(generateFolder, "MiosDeviceCreator.cs"));
        }

        private static String GetGenerateFolder(Int32 folderCount)
        {
            return Path.Combine(_outputFolder.Name,
                folderCount == 0 ? "Generate" : String.Format("Generate_{0}", folderCount));
        }

        public static void GenerateFiles()
        {
            if (_outputFolder == null)
                throw new InvalidOperationException("MiosFiles.LoadFiles(...) has to be called before MiosFiles.GenerateFiles()!");

            if ((_serviceFiles == null) || (_deviceFiles == null))
                throw new InvalidOperationException("File list are not initialized!");

            String newFolder;

            for (var folderCount = 0; Directory.Exists(newFolder = GetGenerateFolder(folderCount)); folderCount++)
            {
            }

            Directory.CreateDirectory(newFolder);
            Directory.CreateDirectory(Path.Combine(newFolder, "DeviceFiles"));
            Directory.CreateDirectory(Path.Combine(newFolder, "ServiceFiles"));

            foreach (var deviceFile in _deviceFiles.SortedItems)
            {
                deviceFile.Generate(newFolder);
            }

            foreach (var serviceFile in _serviceFiles.SortedItems)
            {
                serviceFile.Generate(newFolder);
            }

            GenerateMiosDeviceClass(newFolder);
        }
        #endregion

        #region TypeOfMiosFile method

        internal enum Types
        {
            DeviceFile,
            ServiceFile,
            NotSupported
        }

        internal static Types TypeOfMiosFile(String filename)
        {
            if (String.IsNullOrEmpty(filename))
                return Types.NotSupported;

            var lowerFilename = filename.ToLower().Trim();
            var fileExtension = Path.GetExtension(lowerFilename);

            if (String.IsNullOrEmpty(lowerFilename) || String.IsNullOrEmpty(fileExtension) || (fileExtension != ".xml"))
                return Types.NotSupported;

            if (lowerFilename.StartsWith("s_"))
                return Types.ServiceFile;

            return lowerFilename.StartsWith("d_") ? Types.DeviceFile : Types.NotSupported;
        }

        #endregion

        #region RetrieveClassName method

        internal static String RetrieveClassName(String filename)
        {
            if (String.IsNullOrEmpty(filename))
                throw new ArgumentNullException("filename");

            if (TypeOfMiosFile(filename) == Types.NotSupported)
                throw new ArgumentOutOfRangeException(String.Format("Expecting an XML file with prefix 'S_' or 'D_'. [{0}]", filename));

            var onlyFilename = Path.GetFileNameWithoutExtension(filename).Trim();

            return onlyFilename.Substring(2);
        }

        #endregion

        #region File Collections : ServiceFile and DeviceFile
        private static ItemsCollection<ServiceFile> _serviceFiles;
        private static ItemsCollection<DeviceFile> _deviceFiles;

        #region Common Methods for File Collections
        internal static MiosFile GetFile(String filename)
        {
            switch (TypeOfMiosFile(filename))
            {
                case Types.DeviceFile:
                    return _deviceFiles.GetByName(filename);

                case Types.ServiceFile:
                    return _serviceFiles.GetByName(filename);

                default:
                    throw new InvalidOperationException("File type not supported!");
            }
        }

        private static void AddFile(MiosFile miosFile)
        {
            if (miosFile.GetType() == typeof(ServiceFile))
            {
                _serviceFiles.Add((ServiceFile) miosFile, false);
            }
            else if (miosFile.GetType() == typeof(DeviceFile))
            {
                _deviceFiles.Add((DeviceFile) miosFile, false);
            }
            else
            {
                throw new InvalidOperationException("File type not supported!");                
            }
        }

        internal static ServiceNode FindServiceNode(String serviceId)
        {
            return _deviceFiles.SortedItems.SelectMany(deviceFile => deviceFile.ServiceNodes.SortedItems)
                                           .FirstOrDefault(serviceNode => serviceNode.Name == serviceId);
        }

        #endregion
        #endregion
    }
}
